﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace HirelSeriesH.Tools
{
    class CommPort
    {
        //public int PortNum;
        public string PortName;
        public int BaudRate;
        public byte ByteSize;
        public byte Parity; // 0-4=no,odd,even,mark,space 
        public byte StopBits; // 0,1,2 = 1, 1.5, 2 
        public int ReadTimeout;

        //comm port win32 file handle 
        private int hComm = -1;

        public bool Opened = false;

        //win32 api constants 
        private const uint GENERIC_READ = 0x80000000;
        private const uint GENERIC_WRITE = 0x40000000;
        private const int OPEN_EXISTING = 3;
        private const int INVALID_HANDLE_VALUE = -1;

        [StructLayout(LayoutKind.Sequential)]
        public struct DCB
        {
            //taken from c struct in platform sdk 
            public int DCBlength; // sizeof(DCB) 
            public int BaudRate; // current baud rate 
            /* these are the c struct bit fields, bit twiddle flag to set 
            public int fBinary; // binary mode, no EOF check 
            public int fParity; // enable parity checking 
            public int fOutxCtsFlow; // CTS output flow control 
            public int fOutxDsrFlow; // DSR output flow control 
            public int fDtrControl; // DTR flow control type 
            public int fDsrSensitivity; // DSR sensitivity 
            public int fTXContinueOnXoff; // XOFF continues Tx 
            public int fOutX; // XON/XOFF out flow control 
            public int fInX; // XON/XOFF in flow control 
            public int fErrorChar; // enable error replacement 
            public int fNull; // enable null stripping 
            public int fRtsControl; // RTS flow control 
            public int fAbortOnError; // abort on error 
            public int fDummy2; // reserved 
            */
            public uint flags;
            public ushort wReserved; // not currently used 
            public ushort XonLim; // transmit XON threshold 
            public ushort XoffLim; // transmit XOFF threshold 
            public byte ByteSize; // number of bits/byte, 4-8 
            public byte Parity; // 0-4=no,odd,even,mark,space 
            public byte StopBits; // 0,1,2 = 1, 1.5, 2 
            public char XonChar; // Tx and Rx XON character 
            public char XoffChar; // Tx and Rx XOFF character 
            public char ErrorChar; // error replacement character 
            public char EofChar; // end of input character 
            public char EvtChar; // received event character 
            public ushort wReserved1; // reserved; do not use 
        }
        //public struct DCB
        //{
        //    ///<summary>
        //    /// DCB 长度
        //    ///</summary>
        //    public int DCBlength;
        //    ///<summary>
        //    /// 指定当前波特率
        //    ///</summary>
        //    public int BaudRate;
        //    ///<summary>
        //    /// 标志位
        //    ///</summary>
        //    public uint flags;
        //    ///<summary>
        //    /// 未使用,必须为0
        //    ///</summary>
        //    public ushort wReserved;
        //    ///<summary>
        //    /// 指定在XON字符发送这前接收缓冲区中可允许的最小字节数
        //    ///</summary>
        //    public ushort XonLim;
        //    ///<summary>
        //    /// 指定在XOFF字符发送这前接收缓冲区中可允许的最小字节数
        //    ///</summary>
        //    public ushort XoffLim;
        //    ///<summary>
        //    /// 指定端口当前使用的数据位
        //    ///</summary>
        //    public byte ByteSize;
        //    ///<summary>
        //    /// 指定端口当前使用的奇偶校验方法,可能为:EVENPARITY,MARKPARITY,NOPARITY,ODDPARITY 0-4=no,odd,even,mark,space
        //    ///</summary>
        //    public byte Parity;
        //    ///<summary>
        //    /// 指定端口当前使用的停止位数,可能为:ONESTOPBIT,ONE5STOPBITS,TWOSTOPBITS 0,1,2 = 1, 1.5, 2
        //    ///</summary>
        //    public byte StopBits;
        //    ///<summary>
        //    /// 指定用于发送和接收字符XON的值 Tx and Rx XON character
        //    ///</summary>
        //    public byte XonChar;
        //    ///<summary>
        //    /// 指定用于发送和接收字符XOFF值 Tx and Rx XOFF character
        //    ///</summary>
        //    public byte XoffChar;
        //    ///<summary>
        //    /// 本字符用来代替接收到的奇偶校验发生错误时的值
        //    ///</summary>
        //    public byte ErrorChar;
        //    ///<summary>
        //    /// 当没有使用二进制模式时,本字符可用来指示数据的结束
        //    ///</summary>
        //    public byte EofChar;
        //    ///<summary>
        //    /// 当接收到此字符时,会产生一个事件
        //    ///</summary>
        //    public byte EvtChar;
        //    ///<summary>
        //    /// 未使用
        //    ///</summary>
        //    public ushort wReserved1;
        //}

        [StructLayout(LayoutKind.Sequential)]
        private struct COMMTIMEOUTS
        {
            public int ReadIntervalTimeout;
            public int ReadTotalTimeoutMultiplier;
            public int ReadTotalTimeoutConstant;
            public int WriteTotalTimeoutMultiplier;
            public int WriteTotalTimeoutConstant;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct OVERLAPPED
        {
            public int Internal;
            public int InternalHigh;
            public int Offset;
            public int OffsetHigh;
            public int hEvent;
        }

        [StructLayout(LayoutKind.Sequential)]
        private struct COMSTAT
        {
            /*public int fCtsHold;
            public int fDsrHold;
            public int fRlsdHold;
            public int fXoffHold;
            public int fXoffSent;
            public int fEof;
            public int fTxim;
            public int fReserved;
            public int cbInQue;
            public int cbOutQue;*/
            // Should have a reverse, i don't know why!!!!!
            public int cbOutQue;
            public int cbInQue;
            public int fReserved;
            public int fTxim;
            public int fEof;
            public int fXoffSent;
            public int fXoffHold;
            public int fRlsdHold;
            public int fDsrHold;
            public int fCtsHold;
        }

        [DllImport("kernel32.dll")]
        private static extern int CreateFile(
        string lpFileName, // file name 
        uint dwDesiredAccess, // access mode 
        int dwShareMode, // share mode 
        int lpSecurityAttributes, // SD 
        int dwCreationDisposition, // how to create 
        int dwFlagsAndAttributes, // file attributes 
        int hTemplateFile // handle to template file 
        );
        [DllImport("kernel32.dll")]
        private static extern bool GetCommState(
        int hFile, // handle to communications device 
        ref DCB lpDCB // device-control block 
        );
        [DllImport("kernel32.dll")]
        private static extern bool BuildCommDCB(
        string lpDef, // device-control string 
        ref DCB lpDCB // device-control block 
        );
        [DllImport("kernel32.dll")]
        private static extern bool SetCommState(
        int hFile, // handle to communications device 
        ref DCB lpDCB // device-control block 
        );
        [DllImport("kernel32.dll")]
        private static extern bool GetCommTimeouts(
        int hFile, // handle to comm device 
        ref COMMTIMEOUTS lpCommTimeouts // time-out values 
        );
        [DllImport("kernel32.dll")]
        private static extern bool SetCommTimeouts(
        int hFile, // handle to comm device 
        ref COMMTIMEOUTS lpCommTimeouts // time-out values 
        );
        [DllImport("kernel32.dll")]
        private static extern bool ReadFile(
        int hFile, // handle to file 
        byte[] lpBuffer, // data buffer 
        int nNumberOfBytesToRead, // number of bytes to read 
        ref int lpNumberOfBytesRead, // number of bytes read 
        ref OVERLAPPED lpOverlapped // overlapped buffer 
        );
        [DllImport("kernel32.dll")]
        private static extern bool WriteFile(
        int hFile, // handle to file 
        byte[] lpBuffer, // data buffer 
        int nNumberOfBytesToWrite, // number of bytes to write 
        ref int lpNumberOfBytesWritten, // number of bytes written 
        ref OVERLAPPED lpOverlapped // overlapped buffer 
        );
        [DllImport("kernel32.dll")]
        private static extern bool CloseHandle(
        int hObject // handle to object 
        );
        [DllImport("kernel32.dll")]
        private static extern uint GetLastError();

        [DllImport("kernel32")]
        private static extern bool ClearCommError(
        int hFile, // handle to file
        ref int lpErrors,
        ref COMSTAT lpStat
        );

        public void Open()
        {

            DCB dcbCommPort = new DCB();
            COMMTIMEOUTS ctoCommPort = new COMMTIMEOUTS();


            // OPEN THE COMM PORT. 


            //hComm = CreateFile("COM" + PortNum, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
            hComm = CreateFile(PortName, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);

            // IF THE PORT CANNOT BE OPENED, BAIL OUT. 
            if (hComm == INVALID_HANDLE_VALUE)
            {
                throw (new ApplicationException("Comm Port Can Not Be Opened"));
            }

            // SET THE COMM TIMEOUTS. 

            GetCommTimeouts(hComm, ref ctoCommPort);
            ctoCommPort.ReadTotalTimeoutConstant = ReadTimeout;
            ctoCommPort.ReadTotalTimeoutMultiplier = 0;
            ctoCommPort.WriteTotalTimeoutMultiplier = 0;
            //ctoCommPort.WriteTotalTimeoutConstant = 0;
            ctoCommPort.WriteTotalTimeoutConstant = 20;//毫秒
            ctoCommPort.ReadIntervalTimeout = 10;//2021-10-8
            SetCommTimeouts(hComm, ref ctoCommPort);

            // SET BAUD RATE, PARITY, WORD SIZE, AND STOP BITS. 
            GetCommState(hComm, ref dcbCommPort);
            dcbCommPort.BaudRate = BaudRate;
            dcbCommPort.flags = 0;
            //dcb.fBinary=1; 
            dcbCommPort.flags |= 1;
            if (Parity > 0)
            {
                //dcb.fParity=1 
                dcbCommPort.flags |= 2;
            }
            dcbCommPort.Parity = Parity;
            dcbCommPort.ByteSize = ByteSize;
            dcbCommPort.StopBits = StopBits;

            //add by sean, 2021-08-21
            dcbCommPort.flags = 0;

            //------------------------------
            //SetDcbFlag(0, 1, dcbCommPort);            // 二进制方式
            //SetDcbFlag(1, (Parity == 0) ? 0 : 1, dcbCommPort);
            //SetDcbFlag(2, 0, dcbCommPort);            // 不用CTS检测发送流控制
            //SetDcbFlag(3, 0, dcbCommPort);            // 不用DSR检测发送流控制
            //SetDcbFlag(4, 0, dcbCommPort);            // 禁止DTR流量控制
            //SetDcbFlag(6, 0, dcbCommPort);            // 对DTR信号线不敏感
            //SetDcbFlag(9, 1, dcbCommPort);            // 检测接收缓冲区
            //SetDcbFlag(8, 0, dcbCommPort);            // 不做发送字符控制
            //SetDcbFlag(10, 0, dcbCommPort);           // 是否用指定字符替换校验错的字符
            //SetDcbFlag(11, 0, dcbCommPort);           // 保留NULL字符
            //SetDcbFlag(12, 0, dcbCommPort);           // 允许RTS流量控制
            //SetDcbFlag(14, 0, dcbCommPort);           // 发送错误后，继续进行下面的读写操作
            //--------------------------------

            dcbCommPort.wReserved = 0;
            dcbCommPort.XonLim = 0;
            dcbCommPort.XoffLim = 0;
            //dcbCommPort.XonChar = 0;
            //dcbCommPort.XoffChar = 0;
            //dcbCommPort.ErrorChar = 0;
            //dcbCommPort.EofChar = 0;
            //dcbCommPort.EvtChar = 0;
            dcbCommPort.wReserved1 = 0;

            dcbCommPort.DCBlength = Marshal.SizeOf(dcbCommPort);

            if (!SetCommState(hComm, ref dcbCommPort))
            {
                uint ErrorNum = GetLastError();
                throw (new ApplicationException("SetCommState error, code:" + ErrorNum.ToString()));
            }
            //unremark to see if setting took correctly 
            //DCB dcbCommPort2 = new DCB(); 
            //GetCommState(hComm, ref dcbCommPort2); 
            Opened = true;


            //========================================

            //DCB dcb = new DCB();
            //COMMTIMEOUTS ctoCommPort = new COMMTIMEOUTS();

            //// 打开串口
            ////hComm = CreateFile(Port, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
            ////if (hComm == INVALID_HANDLE_VALUE)
            ////{
            ////    return -1;
            ////}

            //hComm = CreateFile("COM" + PortNum, GENERIC_READ | GENERIC_WRITE, 0, 0, OPEN_EXISTING, 0, 0);
            //// IF THE PORT CANNOT BE OPENED, BAIL OUT. 
            //if (hComm == INVALID_HANDLE_VALUE)
            //{
            //    throw (new ApplicationException("Comm Port Can Not Be Opened"));
            //}

            //// 设置通信超时时间
            //GetCommTimeouts(hComm, ref ctoCommPort);
            //ctoCommPort.ReadTotalTimeoutConstant = ReadTimeout;
            //ctoCommPort.ReadTotalTimeoutMultiplier = 0;
            //ctoCommPort.WriteTotalTimeoutMultiplier = 0;
            //ctoCommPort.WriteTotalTimeoutConstant = 0;
            //SetCommTimeouts(hComm, ref ctoCommPort);

            //// 设置串口参数
            //GetCommState(hComm, ref dcb);
            //dcb.DCBlength = Marshal.SizeOf(dcb);
            //dcb.BaudRate = BaudRate;
            //dcb.flags = 0;
            //dcb.ByteSize = (byte)ByteSize;
            //dcb.StopBits = StopBits;
            //dcb.Parity = (byte)Parity;

            ////------------------------------
            //SetDcbFlag(0, 1, dcb);            // 二进制方式
            //SetDcbFlag(1, (Parity == 0) ? 0 : 1, dcb);
            //SetDcbFlag(2, 0, dcb);            // 不用CTS检测发送流控制
            //SetDcbFlag(3, 0, dcb);            // 不用DSR检测发送流控制
            //SetDcbFlag(4, 0, dcb);            // 禁止DTR流量控制
            //SetDcbFlag(6, 0, dcb);            // 对DTR信号线不敏感
            //SetDcbFlag(9, 1, dcb);            // 检测接收缓冲区
            //SetDcbFlag(8, 0, dcb);            // 不做发送字符控制
            //SetDcbFlag(10, 0, dcb);           // 是否用指定字符替换校验错的字符
            //SetDcbFlag(11, 0, dcb);           // 保留NULL字符
            //SetDcbFlag(12, 0, dcb);           // 允许RTS流量控制
            //SetDcbFlag(14, 0, dcb);           // 发送错误后，继续进行下面的读写操作
            ////--------------------------------
            //dcb.wReserved = 0;                       // 没有使用，必须为0      
            //dcb.XonLim = 0;                          // 指定在XOFF字符发送之前接收到缓冲区中可允许的最小字节数
            //dcb.XoffLim = 0;                         // 指定在XOFF字符发送之前缓冲区中可允许的最小可用字节数
            //dcb.XonChar = 0;                         // 发送和接收的XON字符
            //dcb.XoffChar = 0;                        // 发送和接收的XOFF字符
            //dcb.ErrorChar = 0;                       // 代替接收到奇偶校验错误的字符
            //dcb.EofChar = 0;                         // 用来表示数据的结束     
            //dcb.EvtChar = 0;                         // 事件字符，接收到此字符时，会产生一个事件       
            //dcb.wReserved1 = 0;                      // 没有使用

            //if (!SetCommState(hComm, ref dcb))
            //{
            //    //return -2;
            //    throw (new ApplicationException("Comm Port Can Not Be Opened"));
            //}
            //Opened = true;


        }

        public void Close()
        {
            if (hComm != INVALID_HANDLE_VALUE)
            {
                CloseHandle(hComm);
            }
            Opened = false;
        }
        public byte[] Read(int NumBytes)
        {
            byte[] BufBytes;
            byte[] OutBytes;
            BufBytes = new byte[NumBytes];
            if (hComm != INVALID_HANDLE_VALUE)
            {
                OVERLAPPED ovlCommPort = new OVERLAPPED();
                int BytesRead = 0;
                ReadFile(hComm, BufBytes, NumBytes, ref BytesRead, ref ovlCommPort);
                OutBytes = new byte[BytesRead];
                Array.Copy(BufBytes, OutBytes, BytesRead);
            }
            else
            {
                throw (new ApplicationException("Comm Port Not Open"));
            }
            return OutBytes;
        }

        public bool Write(byte[] WriteBytes)
        {
            if (hComm != INVALID_HANDLE_VALUE)
            {
                //DCB dcbCommPort = new DCB();
                //GetCommState(hComm, ref dcbCommPort);

                OVERLAPPED ovlCommPort = new OVERLAPPED();
                int BytesWritten = 0;
                bool res = WriteFile(hComm, WriteBytes, WriteBytes.Length, ref BytesWritten, ref ovlCommPort);
                return res;
            }
            else
            {
                throw (new ApplicationException("Comm Port Not Open"));
            }
        }
        //public void Write9bit(byte[] WriteBytes)
        //{
        //    if (hComm != INVALID_HANDLE_VALUE)
        //    {
        //        int count = WriteBytes.Length;
        //        if (count < 3) return;//命令小于3个字节则返回

        //        DCB dcbCommPort = new DCB();
        //        GetCommState(hComm, ref dcbCommPort);
        //        dcbCommPort.Parity = 4;//space
        //        int BytesWritten = 0;
        //        OVERLAPPED ovlCommPort = new OVERLAPPED();
        //        List<byte> lbytes = new List<byte>();
        //        //发送第一字节"AA"
        //        lbytes.Add(WriteBytes[0]);
        //        WriteFile(hComm, lbytes.ToArray(), lbytes.ToArray().Length, ref BytesWritten, ref ovlCommPort);

        //        //发送第二字节地址"Bx"
        //        lbytes.Clear();
        //        lbytes.Add(WriteBytes[1]);
        //        dcbCommPort.Parity = 3;//mark
        //        WriteFile(hComm, lbytes.ToArray(), lbytes.ToArray().Length, ref BytesWritten, ref ovlCommPort);

        //        //发送第3字节以后
        //        lbytes.Clear();
        //        for (int i = 2; i < count; i++)
        //        {
        //            lbytes.Add(WriteBytes[i]);
        //        }
        //        dcbCommPort.Parity = 4;//mark
        //        WriteFile(hComm, lbytes.ToArray(), lbytes.ToArray().Length, ref BytesWritten, ref ovlCommPort);

        //        //WriteFile(hComm, WriteBytes, WriteBytes.Length, ref BytesWritten, ref ovlCommPort);
        //    }
        //    else
        //    {
        //        throw (new ApplicationException("Comm Port Not Open"));
        //    }
        //}

        public void Write9bit(byte[] WriteBytes)
        {
            if (hComm != INVALID_HANDLE_VALUE)
            {
                int count = WriteBytes.Length;

                int BytesWritten = 0;
                OVERLAPPED ovlCommPort = new OVERLAPPED();
                //WriteFile(hComm, WriteBytes, WriteBytes.Length, ref BytesWritten, ref ovlCommPort);
                //return;

                if (count < 2) return;//命令小于2个字节则返回

                DCB dcbCommPort = new DCB();
                GetCommState(hComm, ref dcbCommPort);
                dcbCommPort.Parity = 3;//3-Mark/4-space
                if (!SetCommState(hComm, ref dcbCommPort))
                {
                    uint ErrorNum = GetLastError();
                    throw (new ApplicationException("SetCommState error, code:" + ErrorNum.ToString()));
                }


                List<byte> lbytes = new List<byte>();
                //发送第一字节——硬件地址
                lbytes.Add(WriteBytes[0]);

                WriteFile(hComm, lbytes.ToArray(), lbytes.ToArray().Length, ref BytesWritten, ref ovlCommPort);

                //发送第2字节以后
                lbytes.Clear();
                for (int i = 1; i < count; i++)
                {
                    lbytes.Add(WriteBytes[i]);
                }
                dcbCommPort.Parity = 4;//space
                if (!SetCommState(hComm, ref dcbCommPort))
                {
                    uint ErrorNum = GetLastError();
                    throw (new ApplicationException("SetCommState error, code:" + ErrorNum.ToString()));
                }

                WriteFile(hComm, lbytes.ToArray(), lbytes.ToArray().Length, ref BytesWritten, ref ovlCommPort);

            }
            else
            {
                throw (new ApplicationException("Comm Port Not Open"));
            }
        }
        //测试校验位变换时间
        public void Write9bit(byte[] WriteBytes, ref long ms)
        {
            if (hComm != INVALID_HANDLE_VALUE)
            {
                int count = WriteBytes.Length;

                int BytesWritten = 0;
                OVERLAPPED ovlCommPort = new OVERLAPPED();
                //WriteFile(hComm, WriteBytes, WriteBytes.Length, ref BytesWritten, ref ovlCommPort);
                //return;

                if (count < 2) return;//命令小于2个字节则返回

                DCB dcbCommPort = new DCB();
                GetCommState(hComm, ref dcbCommPort);
                dcbCommPort.Parity = 3;//3-Mark/4-space
                if (!SetCommState(hComm, ref dcbCommPort))
                {
                    uint ErrorNum = GetLastError();
                    throw (new ApplicationException("SetCommState error, code:" + ErrorNum.ToString()));
                }


                List<byte> lbytes = new List<byte>();
                //发送第一字节——硬件地址
                lbytes.Add(WriteBytes[0]);

                Stopwatch st = new Stopwatch();
                st.Start();

                WriteFile(hComm, lbytes.ToArray(), lbytes.ToArray().Length, ref BytesWritten, ref ovlCommPort);

                //发送第2字节以后
                lbytes.Clear();
                for (int i = 1; i < count; i++)
                {
                    lbytes.Add(WriteBytes[i]);
                }
                dcbCommPort.Parity = 4;//space
                if (!SetCommState(hComm, ref dcbCommPort))
                {
                    uint ErrorNum = GetLastError();
                    throw (new ApplicationException("SetCommState error, code:" + ErrorNum.ToString()));
                }

                st.Stop();

                WriteFile(hComm, lbytes.ToArray(), lbytes.ToArray().Length, ref BytesWritten, ref ovlCommPort);


                ms = st.ElapsedTicks;//st.ElapsedMilliseconds;
            }
            else
            {
                throw (new ApplicationException("Comm Port Not Open"));
            }
        }
        ///<summary>
        /// 设置DCB标志位
        ///</summary>
        ///<param name="whichFlag"></param>
        ///<param name="setting"></param>
        ///<param name="dcb"></param>
        //internal void SetDcbFlag(int whichFlag, int setting, DCB dcb)
        //{
        //    uint num;
        //    setting = setting << whichFlag;
        //    if ((whichFlag == 4) || (whichFlag == 12))
        //    {
        //        num = 3;
        //    }
        //    else if (whichFlag == 15)
        //    {
        //        num = 0x1ffff;
        //    }
        //    else
        //    {
        //        num = 1;
        //    }
        //    dcb.flags &= ~(num << whichFlag);
        //    dcb.flags |= (uint)setting;
        //}
        public int GetNumToRead()
        {
            int num = 0;
            int err = 0;
            COMSTAT comstat = new COMSTAT();

            if (hComm != INVALID_HANDLE_VALUE)
            {
                if (ClearCommError(hComm, ref err, ref comstat))
                {
                    num = comstat.cbInQue;
                }
            }

            return num;
        }
    }
}
